/**
 * Copyright (c) [2025] [AGIROS] 
 * [travoddsgen] is licensed under Mulan PSL v2. 
 * You can use this software according to the terms and conditions of the Mulan PSL v2. 
 * You may obtain a copy of Mulan PSL v2 at: 
 *          http://license.coscl.org.cn/MulanPSL2 
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 
 * See the Mulan PSL v2 for more details.
 */ 

#include "ParameterProcessor.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <stdexcept>

#ifdef _WIN32
#include <windows.h>
#define PATH_MAX 32767
#else
#include <sys/stat.h>
#include <limits.h>
#endif

ParameterProcessor::ParameterProcessor(int argc, char* argv[]) {
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if (arg.rfind("--", 0) == 0) {
            std::string key = arg.substr(2);
            std::string value;
            if (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
                value = argv[++i];
            }
            parameters[key] = value;
        }
    }
}

std::string ParameterProcessor::getFileName(const std::string& path) {
    std::string fileName = path;
    size_t pos = path.find_last_of("/\\");
    if (pos != std::string::npos) {
        fileName = path.substr(pos + 1);
    }
    pos = fileName.find_last_of(".");
    if (pos != std::string::npos) {
        fileName = fileName.substr(0, pos);
    }
    return fileName;
}

bool ParameterProcessor::fileExist(const std::string& filePath) {
    std::ifstream file(filePath);
    return file.good();
}

bool ParameterProcessor::dirExist(const std::string& dirPath) {
#ifdef _WIN32
    DWORD ftyp = GetFileAttributesA(dirPath.c_str());
    return (ftyp != INVALID_FILE_ATTRIBUTES && (ftyp & FILE_ATTRIBUTE_DIRECTORY));
#else
    struct stat buffer;
    return (stat(dirPath.c_str(), &buffer) == 0 && S_ISDIR(buffer.st_mode));
#endif
}

bool ParameterProcessor::normalizePath(std::string& path) {
    char buffer[PATH_MAX];
#ifdef _WIN32
    if (GetFullPathName(path.c_str(), MAX_PATH, buffer, nullptr)) {
        path = buffer;
    } else {
        return false;
    }
#else
    if (realpath(path.c_str(), buffer)) {
        path = buffer;
    } else {
        return false;
    }
#endif
    std::replace(path.begin(), path.end(), '\\', '/');
    path.erase(path.find_last_not_of('/') + 1);
    return true;
}

bool ParameterProcessor::validate() {
    std::vector<std::string> required = {"input", "output_dir", "language"};
    for (auto& key : required) {
        if (!has(key)) {
            printHelp();
            throw std::invalid_argument("Missing required parameter: --" + key);
        }
    }
    // Check input file
    std::string input = get("input");
    if (!fileExist(input)) {
        printHelp();
        throw std::invalid_argument("Could not open file: " + input);
    }
    // Normalize input path
    if (!normalizePath(input)) {
        printHelp();
        throw std::invalid_argument("Could not normalize path: " + input);
    }
    set("input", input);

    // Check output directory
    std::string outputDir = get("output_dir");
    if (!dirExist(outputDir)) {
        printHelp();
        throw std::invalid_argument("Could not open directory: " + outputDir);
    }
    if (!normalizePath(outputDir)) {
        printHelp();
        throw std::invalid_argument("Could not normalize path: " + outputDir);
    }
    set("output_dir", outputDir);

    // Check language
    std::string lang = get("language");
    static const std::vector<std::string> supported = {"c", "c++", "java", "python", "rust"};
    if (std::find(supported.begin(), supported.end(), lang) == supported.end()) {
        printHelp();
        throw std::invalid_argument("Unsupported language: " + lang);
    }
    std::transform(lang.begin(), lang.end(), lang.begin(), ::tolower);
    set("language", lang);

    // Process include_path
    if (has("include_path")) {
        std::string includePath = get("include_path");
        size_t pos = 0;
        while (pos < includePath.size()) {
            size_t next = includePath.find(';', pos);
            std::string curIncludePath = includePath.substr(pos, next - pos);
            if (!normalizePath(curIncludePath)) {
                std::cout << "Could not normalize path: " << curIncludePath << std::endl;
            }
            this->includePaths.push_back(curIncludePath);
            if (next == std::string::npos) break;
            pos = next + 1;
        }
    }

    // Add IDL directory to include_paths
    std::string idlDir = input.substr(0, input.find_last_of("/\\"));
    this->includePaths.push_back(idlDir);
    return true;
}

std::string ParameterProcessor::get(const std::string& key, const std::string& defaultVal) const {
    auto it = parameters.find(key);
    return (it != parameters.end()) ? it->second : defaultVal;
}

const std::vector<std::string>& ParameterProcessor::getIncludePaths() const {
    return includePaths;
}

void ParameterProcessor::set(const std::string& key, const std::string& value) {
    parameters[key] = value;
}

bool ParameterProcessor::has(const std::string& key) const {
    return parameters.find(key) != parameters.end();
}

void ParameterProcessor::printHelp() const {
    std::cerr << "Usage: travoddsgen --input <file> --output_dir <dir> --language <c|c++|java|python|rust>\n"
        << "Options:\n"
        << "  --include_path <paths>    Semicolon-separated include paths (optional)\n"
        << "  --ros2                    Enable ROS2 support (optional, default false)\n"
        << "  --rpc                     Auto add RPC members to structs ending with _Request or _Response (optional, default false)\n";
}
